package goo

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"math/rand"
	"net/http"
	"sync"
	"time"
)

var sessionManger *SessionManger //全局session 管理器
var onceSessionManger sync.Once

type Session interface {
	Set(string, interface{}) // 设置Session
	Get(string) interface{}  // 获取Session
	Del(string)              // 删除Session
	SID() string
}
type SessionProvider interface {
	GetSession(ctx *Context) Session
	DestroySession(ctx *Context)
	GcSession()
}

type SessionManger struct {
	Sessions    map[string]Session
	CookieName  string
	Provider    SessionProvider
	MaxLifeTime int64
}

func SessionMangerInstance() *SessionManger {
	onceSessionManger.Do(func() {
		sessionManger = &SessionManger{}
	})
	return sessionManger
}

func (sm *SessionManger) Init(provider SessionProvider, cookieName string, maxLifeTime int64) *SessionManger {
	sm.CookieName = cookieName
	sm.MaxLifeTime = maxLifeTime
	sm.Provider = provider
	provider.GcSession()
	return sessionManger
}

func (sm *SessionManger) GenSID() string {
	s1 := time.Now().UnixNano()
	s2 := rand.Float64()
	s3 := fmt.Sprintf("%d,%f,%f,%d", s1, s2, s2, s1)
	h := md5.New()
	h.Write([]byte(s3))
	return hex.EncodeToString(h.Sum(nil))
}

//内存session生产
type SessionMemoryProvider struct {
	lock     sync.Mutex
	sessions map[string]*SessionMemory
}

func (sm *SessionMemoryProvider) GetSession(ctx *Context) Session {
	sm.lock.Lock()
	defer sm.lock.Unlock()
	session, ok := sm.sessions[ctx.GetCookieVal(SessionMangerInstance().CookieName)]
	if ok {
		return session
	}
	s := &SessionMemory{SessionAttrs: make(map[string]interface{}), SessionId: SessionMangerInstance().GenSID(), Expire: SessionMangerInstance().MaxLifeTime, LastTimeAccessed: time.Now()}
	sm.sessions[s.SessionId] = s
	ctx.SetCookie(&http.Cookie{Name: SessionMangerInstance().CookieName, Value: s.SessionId, HttpOnly: true})
	return s
}

func (sm *SessionMemoryProvider) DestroySession(ctx *Context) {
	sm.lock.Lock()
	defer sm.lock.Unlock()
	delete(sm.sessions, ctx.GetCookieVal(SessionMangerInstance().CookieName))
}

func (sm *SessionMemoryProvider) GcSession() {
	sm.lock.Lock()
	defer sm.lock.Unlock()

	for sid, session := range sm.sessions {
		if session.LastTimeAccessed.Unix()+session.Expire < time.Now().Unix() {
			delete(sm.sessions, sid)
		}
	}

	//定时回收
	go time.AfterFunc(time.Second, func() { sm.GcSession() })
}

//内存session实现
type SessionMemory struct {
	SessionAttrs     map[string]interface{}
	SessionId        string
	Expire           int64
	LastTimeAccessed time.Time
}

func (s *SessionMemory) Set(key string, value interface{}) {
	s.SessionAttrs[key] = value
}

func (s *SessionMemory) Get(key string) interface{} {
	return s.SessionAttrs[key]
}

func (s *SessionMemory) Del(key string) {
	delete(s.SessionAttrs, key)
}

func (s *SessionMemory) SID() string {
	return s.SessionId
}
